#region Copyright and Licensing Notice
// Copyright (C) 2011 Steven Bosnick
// 
// This program is open source software; you can redistribute it 
// and/or modifiy it under the terms of the Eclipse Public License,
// version 1.0.
// 
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A 
// PARTICULAR PURPOSE. See the Eclipse Public License for more
// details.
// 
// You should have received a copy of the Eclipse Public License 
// along with this program; if not, you can find a copy at
// "http://www.opensource.org/licenses/eclipse-1.0.php".
#endregion
using System;
using System.Runtime.InteropServices;
using BitwiseOre.SharedMemory.External;
using BitwiseOre.SharedMemory.JetBrains;
using Moq;
using NUnit.Framework;

namespace BitwiseOre.Tests.SharedMemory.Primatives
{
    [TestFixture,UsedImplicitly]
    public sealed class SharedIntArrayFixture : AssertionHelper
    {
        [Test(Description = "Ensures that creating a shared int array with a null buffer throws an exception")]
        public void Create_WithNullBuffer()
        {
            Expect(() => new SharedIntArray(null, 0, 5),
                   Throws.InstanceOf<ArgumentNullException>());
        }

        [Test(Description = "Ensures that creating a shared int array with a negative offset throws an exception")]
        public void Create_WithNegativeOffset()
        {
            Expect(() => new SharedIntArray(new Mock<IBuffer>().Object, -2, 5),
                   Throws.InstanceOf<ArgumentOutOfRangeException>());
        }

        [Test(Description = "Ensurest that creating a shared int array with a negative count throws an expception")]
        public void Create_WithNegativeCount()
        {
            Expect(() => new SharedIntArray(new Mock<IBuffer>().Object, 0, -2),
                   Throws.InstanceOf<ArgumentOutOfRangeException>());
        }

        [Test(Description = "Ensures that creating a shared int array with more elements than the buffer has room for throws an exception")]
        public void Create_WithCountPastEndOfBuffer()
        {
            var buffer = new Mock<IBuffer>();
            const int offset = 5*sizeof (int);
            const int count = 5;
            buffer.Setup(b => b.ByteLength).Returns(9*sizeof (int));

            Expect(() => new SharedIntArray(buffer.Object, offset, count),
                   Throws.InstanceOf<ArgumentOutOfRangeException>());
        }

        [Test(Description = "Checks that the indexer setter delgates to the buffer")]
        public void ThisSetter_WritesToBuffer()
        {
            var buffer = new FakeBuffer {ByteLength = 100};
            const int index = 2;
            const int value = 5;
            
            new SharedIntArray(buffer, 0, 5)[index] = value;

            Expect(buffer.LastWriteOffset, EqualTo(index*sizeof(int)));
            Expect(buffer.LastWriteValue, InstanceOf<int>());
            Expect(buffer.LastWriteValue, EqualTo(value));
        }

        [Test(Description = "Checks that the indexer setter accounts for a non-zero offset for the array")]
        public void ThisSetter_WithNonZeroOffset()
        {
            var buffer = new Mock<IBuffer>();
            buffer.SetupGet(b => b.ByteLength).Returns(100);
            const int index = 2;
            const int offset = 10*sizeof (int);
            new SharedIntArray(buffer.Object, offset, 5)[index] = 7;

            buffer.Verify(b => b.Write(It.Is<ulong>(i => i == index*sizeof (int) + offset),
                                       It.IsAny<int>()));
        }

        [Test(Description = "Ensures that the indexer setter throws an excpetion with a negative index.")]
        public void ThisSetter_WithNegativeIndex()
        {
            var buffer = new Mock<IBuffer>();
            buffer.SetupGet(b => b.ByteLength).Returns(100);
            const int index = -1;
            var array = new SharedIntArray(buffer.Object, 0, 5);
            Expect(() => array[index] = 5, Throws.InstanceOf<ArgumentOutOfRangeException>());
        }

        [Test(Description = "Ensures that the indexer setter throws an excepiton with an index greater than the count")]
        public void ThisSetter_WithIndexGreaterThanCount()
        {
            var buffer = new Mock<IBuffer>();
            buffer.SetupGet(b => b.ByteLength).Returns(100);
            const int count = 5;
            const int index = 6;
            var array = new SharedIntArray(buffer.Object, 0, count);
            Expect(() => array[index] = 7, Throws.InstanceOf<ArgumentOutOfRangeException>());
        }

        [Test(Description = "Ensures that the indexer getter throws an exception with a negative index")]
        public void ThisGetter_WithNegativeIndex()
        {
            var buffer = new Mock<IBuffer>();
            buffer.SetupGet(b => b.ByteLength).Returns(100);
            const int index = -1;

            Expect(() => new SharedIntArray(buffer.Object, 0, 5)[index],
                   Throws.InstanceOf<ArgumentOutOfRangeException>());
        }

        [Test(Description = "Ensures that the indexer getter throws an exception with an index greater than the count")]
        public void ThisGetter_WithIndexGreaterThanCount()
        {
            var buffer = new Mock<IBuffer>();
            buffer.SetupGet(b => b.ByteLength).Returns(100);
            const int count = 5;
            const int index = 6;
            Expect(() => new SharedIntArray(buffer.Object, 0, count)[index],
                   Throws.InstanceOf<ArgumentOutOfRangeException>());
            
        }

        [Test(Description = "Checks that the indexer getter delegates to the buffer")]
        public void ThisGetter_ReadFromBuffer()
        {
            var buffer = new Mock<IBuffer>();
            buffer.SetupGet(b => b.ByteLength).Returns(100);
            const int value = 5;
            const int index = 2;
            buffer.Setup(b => b.Read<int>(It.IsAny<ulong>())).Returns(value);

            var result = new SharedIntArray(buffer.Object, 0, 5)[index];

            Expect(result, EqualTo(value));
            buffer.Verify(b => b.Read<int>(It.Is<ulong>(i => i==index*sizeof(int))));
        }

#pragma warning disable 168
        [Test(Description = "Checks that the indexer getter uses both the index and the offset to locate the bytes to read")]
        public void ThisGetter_WithNonZeroOffset()
        {
            var buffer = new Mock<IBuffer>();
            buffer.SetupGet(b => b.ByteLength).Returns(100);
            const int index = 2;
            const int offset = 5*sizeof(int);
            buffer.Setup(b => b.Read<int>(It.IsAny<uint>())).Returns(5);

            var result = new SharedIntArray(buffer.Object, offset, 5)[index];
            
            buffer.Verify(b => b.Read<int>(It.Is<ulong>(i => i == index*sizeof(int) + offset )));
        }
#pragma warning restore 168

        [Test(Description = "Ensures that interlocked compare exchange with a negative index throws an exception")]
        public void InterlockedCompareExchange_WithNegativeIndex()
        {
            var buffer = new Mock<IBuffer>();
            buffer.SetupGet(b => b.ByteLength).Returns(100);
            const int index = -2;
            var array = new SharedIntArray(buffer.Object, 0, 5);

            Expect(() => array.InterlockedCompareExchange(index, 5, 3),
                   Throws.InstanceOf<ArgumentOutOfRangeException>());
        }

        [Test(Description = "Ensures that interlocked compare exchange with an index greater than the count throws an exception")]
        public void InterlockecCompareExchange_WithIndexGreaterThanCount()
        {
            var buffer = new Mock<IBuffer>();
            buffer.SetupGet(b => b.ByteLength).Returns(100);
            const int index = 7;
            const int count = 5;
            var array = new SharedIntArray(buffer.Object, 0, count);

            Expect(() => array.InterlockedCompareExchange(index, 5, 3),
                   Throws.InstanceOf<ArgumentOutOfRangeException>());
        }

        [Test(Description = "Checks that compare exchange does the exchange when the comarand matches")]
        public void InterlockedCompareExchange_WithMatchingComparand()
        {
            var buffer = new TestBuffer();
            const int index = 2;
            const int comparand = 5;
            const int value = 7;
            buffer[index*sizeof (int)] = comparand;
            var array = new SharedIntArray(buffer, 0, 10);

            array.InterlockedCompareExchange(index, comparand, value);

            Expect(buffer[index*sizeof(int)], EqualTo(value));
        }

        [Test(Description = "Checks that compare exchange does not perform the exchange when the comparand does not match")]
        public void InterlockedCompareExchange_WithDifferentComparand()
        {
            var buffer = new TestBuffer();
            const int index = 2;
            const int oldvalue = 3;
            const int comparand = 5;
            const int value = 7;
            buffer[index*sizeof (int)] = oldvalue;
            var array = new SharedIntArray(buffer, 0, 10);

            array.InterlockedCompareExchange(index, comparand, value);

            Expect(buffer[index*sizeof(int)], EqualTo(oldvalue));
            
        }


        private sealed class TestBuffer : IBuffer
        {
            private const int Capacity = 100;
            private readonly unsafe byte* _buffer;

            public TestBuffer()
            {
                unsafe
                {
                    _buffer = (byte*)Marshal.AllocHGlobal(Capacity);
                }
            }

            ~TestBuffer()
            {
                unsafe
                {
                    Marshal.FreeHGlobal((IntPtr)_buffer);
                }
            }

            public unsafe void AcquirePointer(ref byte* pointer)
            {
                pointer = _buffer;
            }

            public void ReleasePointer()
            {
            }

            public T Read<T>(ulong byteOffset) where T : struct
            {
                throw new NotImplementedException();
            }

            public void Write<T>(ulong byteOffset, T value) where T : struct
            {
                throw new NotImplementedException();
            }

            public ulong ByteLength
            {
                get { return Capacity; }
            }

            public int this[ulong byteOffset]
            {
                get
                {
                    unsafe
                    {
                        return _buffer[byteOffset];
                    }
                }
                set
                {
                    unsafe
                    {
                        var address = (int*) (_buffer + byteOffset);
                        *address = value;
                    }
                }
            }
        }

        private sealed class FakeBuffer : IBuffer
        {
            public ulong LastWriteOffset;
            public object LastWriteValue;

            public unsafe void AcquirePointer(ref byte* pointer)
            {
                throw new NotImplementedException();
            }

            public void ReleasePointer()
            {
                throw new NotImplementedException();
            }

            public T Read<T>(ulong byteOffset) where T : struct
            {
                throw new NotImplementedException();
            }

            public void Write<T>(ulong byteOffset, T value) where T : struct
            {
                LastWriteOffset = byteOffset;
                LastWriteValue = value;
            }

            public ulong ByteLength {get; set; }
        }
    }
}